/**
 * @(#) PertLoader.java
 */

package karabut.hw3;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;


public class PertLoader {
	public PertChart load(String fileName) {
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new FileReader(new File(fileName)));
			return load(reader);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
			}
		}
	}

	private PertChart load(BufferedReader reader) throws Exception {
		Map<String, PertNode> nodes = new HashMap<String, PertNode>();
		Map<String, List<String>> edges = new HashMap<String, List<String>>();
		
		// create nodes
		String line;
		while ((line = reader.readLine()) != null) {
			if (line.trim().length() == 0) {
				continue;
			}
			
			String[] tokens = line.split(",");
			String id = tokens[0].trim();
			PertNode node = new PertNode(id, Integer.parseInt(tokens[1].trim()));
			nodes.put(id, node);
			List<String> preds = new ArrayList<String>();
		    for (int i = 2; i < tokens.length; ++i) {
		    	preds.add(tokens[i].trim());
		    }
		    edges.put(id, preds);
		}
		
		// sort topologically and link nodes
		List<PertNode> sorted = new ArrayList<PertNode>();
		while (!nodes.isEmpty()) {
			boolean removed = false;
			Iterator<String> it = nodes.keySet().iterator();
			while (it.hasNext()) {
				String id = it.next();
				PertNode node = nodes.get(id);
				if (edges.get(id).isEmpty()) {
					removed = true;
					it.remove();
					edges.remove(id);
					sorted.add(node);
					
					for (Map.Entry<String, List<String>> entry : edges.entrySet()) {
						Iterator<String> predIter = entry.getValue().iterator();
						while (predIter.hasNext()) {
							String predId = predIter.next();
							if (predId.equals(id)) {
								nodes.get(entry.getKey()).predcessors.add(node);
								predIter.remove();
							}
						}
					}
				}
			}
			
			if (!removed) {
				throw new RuntimeException("Can't topologically sort nodes, seems there are cyclic references.");
			}
		}
		
		// check connected
		checkConnected(sorted.get(sorted.size() - 1), sorted);
		
		// find total durations
		calculateDurations(sorted);		
		
		// find critical path
		markCritical(sorted.get(sorted.size() - 1));
	  
		return new PertChart(sorted.get(0), sorted.get(sorted.size() - 1));
	}

	private void calculateDurations(List<PertNode> sorted) {
		// find out duration
		for (int i = 0; i < sorted.size(); i++) {
			PertNode node = sorted.get(i);
			if (i != 0 && node.predcessors.isEmpty()) {
				// check what there is only one start
				new RuntimeException("Invalid data model: found two diagram starts " + sorted.get(0).getTask() + " and" + node.getTask());
			}
			
			int max = 0;
			for (PertNode pred : node.predcessors) {
				if (pred.totalDuration > max) {
					max = pred.totalDuration;
				}
			}
			node.totalDuration = node.getDuration() + max;
		}
	}
	
	private void markCritical(PertNode node) {
		node.isCritical = true;
		int max = 0;
		for (PertNode pred : node.predcessors) {
			if (pred.totalDuration > max) {
				max = pred.totalDuration;
			}
		}
		for (PertNode pred : node.predcessors) {
			if (pred.totalDuration == max) {
				markCritical(pred);
			}
		}
	}
	
	private void checkConnected(PertNode endNode, List<PertNode> sortedNodes) {
		HashSet<String> traversed = new HashSet<String>();
		traverse(endNode, traversed);
		if (traversed.size() != sortedNodes.size()) {
			throw new RuntimeException("Inconsistent data model. Some task doesn't connected to end node.");
		}
 	}
	
	private void traverse(PertNode node, Set<String> ids) {
		if (ids.contains(node.getTask())) {
			return;
		}
		
		ids.add(node.getTask());
		for (PertNode pred : node.predcessors) {
			traverse(pred, ids);
		}
	}
		
}
